#-------------------------------------------------------------------------------
# Name:        prescribe2pdf.py
# Purpose:     convert Kyocera text (Prescribe) to simple PDF
#
# Author:      polejnik
#
# Version:     1.10
#
# Created:     6.11.2014
# Copyright:   (c) polejnik 2014
#-------------------------------------------------------------------------------
#!/usr/bin/env python

#-------------------------------------------------------------------------------
# Version history:
#-------------------------------------------------------------------------------
# version 1.0   - create class prescribeDocument and doTextPdf
#               - convert text only (ignore fonts, position)
#                   - only one font: Courier normal
#-------------------------------------------------------------------------------
# version 1.01  - add text decoding / reduce to 7-bit text
#-------------------------------------------------------------------------------
# version 1.02  - add positioning (decode prescribe position XXXY)
#               - basic font size tuning
#-------------------------------------------------------------------------------
# version 1.03  - add automatic page detection
#                   - a new page begins with '001'
#-------------------------------------------------------------------------------
# version 1.04  - fonts expanded:
#                   Font Normal = Courier, Size 10
#                   Font Bold = Courier-Bold, Size 10
#                   Font Small = Times-Italic, Size 7
#               - fonts switch tunning
#-------------------------------------------------------------------------------
# version 1.05  - add INI file settings
#                   - only FONT decoding
#-------------------------------------------------------------------------------
# version 1.06  - bugfix (initialize on openDocument() and createTextPdf() )
#               - prepared as import file for project eODD
#-------------------------------------------------------------------------------
# version 1.07  - add logo (JPEG picture/image)
#-------------------------------------------------------------------------------
# version 1.08  - added extended logging
#-------------------------------------------------------------------------------
# version 1.09  - added prescribe printing
#                   - changed fx: readSettings() - added prescribe printing parameters
#                   - added fx: savePrescribeLines(), printPrescribeBlock()
#                   - added fx: updatePrescribeLinesArray(), expandPrescribeLinesArray()
#-------------------------------------------------------------------------------
# version 1.10	- cleaning
#					- isolating from other classes
#					- rewrite to open source
#-------------------------------------------------------------------------------

import sys          # argv
import re           # reg.expr. parsing
import ConfigParser # INI settings
import struct       # jpeg - get size: X,Y
import logging      # logging
import win32print   # prescribe printing

#===============================================================================
# classes
#===============================================================================

#-------------------------------------------------------------------------------
# prescribe document handling
# prescribe document = TXT document with Kyocera keywords (Prescribe)
#-------------------------------------------------------------------------------
class prescribeDocument:

    def __init__(self):

        # FILE variables
        # prescribe document file full name
        self.docFileName = ''
        # prescribe document descriptor
        self.docFileDescriptor = None

        # PRESCRIBE commands, used for parsing ...
        # ... begin and end of prescribe command
        self.commandBegin = '!R!'
        self.commandEnd = 'EXIT;'
        # ... font command
        self.commandFont = 'FONT'
        # ... command for Logo
        self.commandLogo = 'CALL'
        # default font name
        self.defaultFont = 'Default'

        # PRESCRIBE commands, used for printing ...
        # ... command for comment line
        self.commandComment = 'CMNT'
        # ... command for CSet
        self.commandCSet = 'CSET'
        # ... command for Page-Break
        self.commandPage = 'PAGE'
        # output Symbol Set (see ComRef.pdf -> CSET)
        self.outputSymbolSet = '9E'
        # prescribe printer
        self.prescribePrinter = '\printer'

        # DOCUMENT lines
        # document text lines
        self.linesArrayCompact = []
        self.linesArrayExpanded = []
        # document commands
        self.commandsArray = []
        # document fonts
        self.fontsArrayExpanded = []
        # document prescribe lines
        self.prescribeLinesArrayCompact = []
        self.prescribeLinesArrayExpanded = []

        # document pages
        self.maxPages = 0
        self.maxLinesPerPage = 0

        pass

    def readSettings(self, sConfigFileName):
        # logging
        sLog = "Init: reading Prescribe document configuration..."
        print sLog
        logging.info(sLog)
        # open config file
        oConfig = ConfigParser.RawConfigParser()
        oConfig.read(sConfigFileName)
        # set config file section
        sSection = 'Prescribe Document'
        # read CommandBegin from config file
        sKey = 'CommandBegin'
        try:
            self.commandBegin = oConfig.get(sSection, sKey)
        except ConfigParser.Error:
            print "Warning: Could not found settings '%s' in section [%s]. Set to default '%s'" % (sKey,sSection,self.commandBegin)
        # read CommandEnd from config file
        sKey = 'CommandEnd'
        try:
            self.commandEnd = oConfig.get(sSection, sKey)
        except ConfigParser.Error:
            print "Warning: Could not found settings '%s' in section [%s]. Set to default '%s'" % (sKey,sSection,self.commandEnd)
        # read CommandFont from config file
        sKey = 'CommandFont'
        try:
            self.commandFont = oConfig.get(sSection, sKey)
        except ConfigParser.Error:
            print "Warning: Could not found settings '%s' in section [%s]. Set to default '%s'" % (sKey,sSection,self.commandFont)
        # read CommandLogo from config file
        sKey = 'CommandLogo'
        try:
            self.commandLogo = oConfig.get(sSection, sKey)
        except ConfigParser.Error:
            print "Warning: Could not found settings '%s' in section [%s]. Set to default '%s'" % (sKey,sSection,self.commandLogo)
        # read DefaultFont from config file
        sKey = 'DefaultFont'
        try:
            self.defaultFont = oConfig.get(sSection, sKey)
        except ConfigParser.Error:
            print "Warning: Could not found settings '%s' in section [%s]. Set to default '%s'" % (sKey,sSection,self.defaultFont)
        # default font check
        sSection = 'Prescribe to PDF Fonts Mapping'
        try:
            sTemp = oConfig.get(sSection, self.defaultFont)
        except ConfigParser.Error:
            sLog = "ERROR: Default font '%s' not used in section [%s]" % (self.defaultFont, sSection)
            print sLog
            logging.error(sLog)
            sys.exit(3)

        # logging
        sLog = "Init: reading Prescribe printer configuration..."
        print sLog
        logging.info(sLog)
        # set config file section
        sSection = 'Prescribe Printing'
        # read CommandComment from config file
        sKey = 'CommandComment'
        try:
            self.commandComment = oConfig.get(sSection, sKey)
        except ConfigParser.Error:
            print "Warning: Could not found settings '%s' in section [%s]. Set to default '%s'" % (sKey,sSection,self.commandComment)
        # read CommandPage from config file
        sKey = 'CommandPage'
        try:
            self.commandPage = oConfig.get(sSection, sKey)
        except ConfigParser.Error:
            print "Warning: Could not found settings '%s' in section [%s]. Set to default '%s'" % (sKey,sSection,self.commandPage)
        # read CommandCSet from config file
        sKey = 'CommandCSet'
        try:
            self.commandCSet = oConfig.get(sSection, sKey)
        except ConfigParser.Error:
            print "Warning: Could not found settings '%s' in section [%s]. Set to default '%s'" % (sKey,sSection,self.commandCSet)
        # read PrescribeSymbolSet from config file
        sKey = 'PrescribeSymbolSet'
        try:
            self.outputSymbolSet = oConfig.get(sSection, sKey)
        except ConfigParser.Error:
            print "Warning: Could not found settings '%s' in section [%s]. Set to default '%s'" % (sKey,sSection,self.outputSymbolSet)
        # read PrescribePrinter from config file
        sKey = 'PrescribePrinter'
        try:
            self.prescribePrinter = oConfig.get(sSection, sKey)
        except ConfigParser.Error:
            print "Warning: Could not found settings '%s' in section [%s]. Set to default '%s'" % (sKey,sSection,self.prescribePrinter)
        # return
        pass

    def openDocument(self, sDocumentFileName):
        # initialize
        self.docFileName = sDocumentFileName

        # logging
        sLog = " opening document %s" % (sDocumentFileName)
        print sLog
        logging.info(sLog)
        # reset text lines
        self.linesArrayCompact = []
        self.linesArrayExpanded = []
        # reset document commands
        self.commandsArray = []
        # reset document fonts
        self.fontsArrayExpanded = []
        # reset prescribe lines
        self.prescribeLinesArrayCompact = []
        self.prescribeLinesArrayExpanded = []
        # reset document pages
        self.maxPages = 0
        self.maxLinesPerPage = 0
        # try to open document, mode read
        try:
            self.docFileDescriptor = open(self.docFileName, 'r')
        except IOError:
            sLog = "ERROR: Could not open file (%s) to read" % (self.docFileName)
            print sLog
            logging.error(sLog)
            sys.exit(3)
        pass

    def readDocument(self):
        # logging
        sLog = " document parsing to: text lines / fonts / prescribe lines"
        print sLog
        logging.info(sLog)
        # divide document to
            # prescribe commands array
            # text array
        # reset last base line
        iLastBaseLine = 1
        # reset offset cumulated
        iOffsetCumulated = 0
        # reset actual page counter
        bIsFirstLine = True
        iActualPage = 1
        # read document line by line
        for sLine in self.docFileDescriptor:
            # cut line to: BaseLine, Offset, Text
            # reset offset
            iOffset = 0
            # get text, ap osition 5
            sText = sLine[5:]
            # get base line, position 1,2,3
            sBaseLine = sLine[0:3].strip()
            if (sBaseLine == ''):
                iBaseLine = iLastBaseLine
                # get offset
                sOffset = sLine[3:5].strip()
                iOffset = int(sOffset)
                # save offset cumulated
                iOffsetCumulated += iOffset
            else:
                # save line position
                iLastBaseLine = int(sBaseLine)
                # reset offset cumulated
                iOffsetCumulated = 0
                # detect new page, always begins with '001'
                if (iLastBaseLine == 1):
                    # ignore if it is first line in file
                    if not bIsFirstLine:
                        # new page detected
                        iActualPage += 1
                    # reset flag
                    bIsFirstLine = False
            # calculate line position
            iLinePosition = iLastBaseLine + iOffsetCumulated
            # extract prescribe commands from text
            sCommandBlockMask = self.commandBegin + '(.+)' + self.commandEnd
            pPattern = re.compile(sCommandBlockMask, re.M)
            aResult = re.findall(pPattern, sText)
            for sAllCommands in aResult:
                # get array from command block
                sCommandList = sAllCommands.strip().split(';')
                # build one command array from
                # NOTE: this commands are apllied on next line (Line + 1)
                self.updateCommandsArray(iActualPage, iLinePosition + 1, sCommandList)

            # append page command, if new page
            if (sBaseLine == '001'):
                sPageCommandBlock = self.commandBegin + ' ' + self.commandPage + '; ' + self.commandEnd
                self.updatePrescribeLinesArray(iActualPage, iLinePosition, sPageCommandBlock)
            # append prescribe line (commands + text) to array
            self.updatePrescribeLinesArray(iActualPage, iLinePosition, sText)

            # create texl only (reduce prescribe commands)
            sCommandBlockMask = '(' + self.commandBegin + '.*' + self.commandEnd + ')'
            pPattern = re.compile(sCommandBlockMask)
            sTextOnly = pPattern.sub('',sText)
            # append text only to lines array, with position
            self.updateLinesArray(iActualPage, iLinePosition, sTextOnly)
        pass

    def getDocumentLines(self):
        # expand document text lines
        self.expandLinesArray()
        # return document text lines
        return self.linesArrayExpanded

    def getDocumentFonts(self):
        # build document fonts
        self.expandFontsArray()
        # return document text lines
        return self.fontsArrayExpanded

    def getDocumentLogo(self):
        # initialize
        bHasLogo = False
        iCommandLogoLen = len(self.commandLogo)
        # loop over all commands
        for dCommands in self.commandsArray:
            # check only first page
            if (dCommands['Page'] == 1):
                # is there LOGO command ?
                for sCommand in dCommands['Commands']:
                    # normalize command
                    sCommand = sCommand.lstrip()
                    # check
                    if (sCommand[0:iCommandLogoLen] == self.commandLogo):
                        bHasLogo = True
                        break
                if (bHasLogo == True):
                    break
        return bHasLogo

    def getLinesPerPage(self):
        # rerutn lines per page
        return self.maxLinesPerPage

    def getTotalPages(self):
        # rerutn total page
        return self.maxPages

    def getDocumentPrescribeLines(self):
        # expand document prescribe lines
        self.expandPrescribeLinesArray()
        # return document prescribe lines
        return self.prescribeLinesArrayExpanded

    def closeDocument(self):
        # logging
        sLog = " closing document %s" % (self.docFileName)
        print sLog
        logging.info(sLog)
        # close opened document
        self.docFileDescriptor.close()
        pass

    def updateLinesArray(self, iPage, iLine, sLineText):
        # prepare line info
        dLineInfo = {'Page': iPage, 'Line': iLine, 'Text': sLineText}
        # if text is not empty
        if (sLineText.strip() != ''):
            # append lines array
            self.linesArrayCompact.append(dLineInfo)
            # update total page nubmers
            if (self.maxPages < iPage):
                self.maxPages = iPage
            # update maximal lines per page
            if (self.maxLinesPerPage < iLine):
                self.maxLinesPerPage = iLine
        pass

    def updateCommandsArray(self, iPage, iLine, sCommandList):
        # prepare commands info
        dCommands = {'Page': iPage, 'Line': iLine, 'Commands': sCommandList}
        # append commands array
        self.commandsArray.append(dCommands)
        pass

    def updatePrescribeLinesArray(self, iPage, iLine, sPrescribeLine):
        # prepare prescribe line info
        dPrescribeLine = {'Page': iPage, 'Line': iLine, 'Text': sPrescribeLine}
        # append prescribe line array
        self.prescribeLinesArrayCompact.append(dPrescribeLine)
        pass

    def expandLinesArray(self):
        # get max lines per page
        iMaxLinesPerPage = self.maxLinesPerPage
        # get total pages
        iMaxPages = self.maxPages
        # create text array, big enough
        for iIndex in range(iMaxLinesPerPage * iMaxPages):
            self.linesArrayExpanded.append('')
        # update text on positions
        for dLineInfo in self.linesArrayCompact:
            # read actual line and page
            iLine = dLineInfo['Line']
            iPage = dLineInfo['Page']
            # calculate total line number
            iTotalLineNum = ((iPage - 1) * iMaxLinesPerPage) + (iLine - 1)
            self.linesArrayExpanded[iTotalLineNum] = dLineInfo['Text']
        pass

    def expandPrescribeLinesArray(self):
        # get max lines per page
        iMaxLinesPerPage = self.maxLinesPerPage
        # get total pages
        iMaxPages = self.maxPages
        # prepare prescribe comment block
        sCommentCommandBlock = self.commandBegin + ' ' + self.commandComment
        # create text array, big enough
        for iIndex in range(iMaxLinesPerPage * iMaxPages):
            self.prescribeLinesArrayExpanded.append('')
        # update text on positions
        for dLineInfo in self.prescribeLinesArrayCompact:
            # read actual text
            sText = dLineInfo['Text'].rstrip()
            # skip prescribe comment command
            if (sCommentCommandBlock in sText):
                continue
            # replace symbol settings
            if (self.commandCSet in sText):
                sText = self.changeSymbolSet(sText)
            # read actual line and page
            iLine = dLineInfo['Line']
            iPage = dLineInfo['Page']
            # calculate total line number
            iTotalLineNum = ((iPage - 1) * iMaxLinesPerPage) + (iLine - 1)
            self.prescribeLinesArrayExpanded[iTotalLineNum] += sText
        pass

    def expandFontsArray(self):
        # get max lines per page
        iMaxLinesPerPage = self.maxLinesPerPage
        # get total pages
        iMaxPages = self.maxPages
        # create text array, big enough
        for iIndex in range(iMaxLinesPerPage * iMaxPages):
            self.fontsArrayExpanded.append(self.defaultFont)
        # loop over all commands
        for dCommands in self.commandsArray:
            # read actual line and page
            iLine = dCommands['Line']
            iPage = dCommands['Page']
            # calculate total line number
            iTotalLineNum = ((iPage - 1) * iMaxLinesPerPage) + (iLine - 1)
            # is there FONT command ?
            for sCommand in dCommands['Commands']:
                # clean command from spaces
                sCommand = sCommand.strip()
                sCommandPart = sCommand[:len(self.commandFont)]
                if (self.commandFont == sCommandPart):
                    # read font nummber
                    sFontNr = sCommand[len(self.commandFont):].strip()
                    # save font number
                    self.fontsArrayExpanded[iTotalLineNum] = sFontNr
        # expand particular font information to lines after
        # reset last font nr
        sLastFontNr = self.defaultFont
        for iIndex in range(iMaxLinesPerPage * iMaxPages):
            # read font nr
            sFontNr = self.fontsArrayExpanded[iIndex]
            # save to last if not default for future use
            if (sFontNr != self.defaultFont):
                sLastFontNr = sFontNr
            # overwrite font with last font nr
            self.fontsArrayExpanded[iIndex] = sLastFontNr
        pass

    def changeSymbolSet(self, sTextLine):
        # replace 'CSET value;' - reg exp. CSET \d{1,2}[A-Z];
        sCSetBlockMask = self.commandCSet + ' \d{1,2}[A-Z];'
        pPattern = re.compile(sCSetBlockMask)
        sReturnText = pPattern.sub(self.commandCSet + ' ' + self.outputSymbolSet + ';', sTextLine)
        # return replaced CSET
        return sReturnText

    def savePrescribeLines(self, aPrescribeLines, sFileName):
        # logging
        sLog = " creating Prescribe file %s" % (sFileName)
        print sLog
        logging.info(sLog)
        # init write buffer
        sWriteBuffer = ''
        # save prescribe lines with CRLF
        for sLine in aPrescribeLines:
            # add CRLF
            sLine = sLine + '\r\n'
            sWriteBuffer += sLine
        # create empty prescribe file
        try:
            fPrscrb = open(sFileName, 'wb')
        except IOError:
            sLog = "ERROR: Could not open file (%s) to write" % (sFileName)
            print sLog
            logging.error(sLog)
            sys.exit(3)
        # write buffer to file
        fPrscrb.write(sWriteBuffer)
        # close prescribe file
        fPrscrb.close()
        # return file content
        return sWriteBuffer

    def printPrescribeBlock(self, sFileContent, sFileName):
        # logging
        sLog = " printing file %s on printer %s" % (sFileName, self.prescribePrinter)
        print sLog
        logging.info(sLog)
        # init printer
        hPrinter = win32print.OpenPrinter (self.prescribePrinter)
        try:
            # begin print job
            hJob = win32print.StartDocPrinter (hPrinter, 1, (sFileName, None, 'RAW'))
            try:
                # print file content
                win32print.WritePrinter (hPrinter, sFileContent)
            finally:
                # end print job
                win32print.EndDocPrinter (hPrinter)
        finally:
            # close printer
            win32print.ClosePrinter (hPrinter)
        # exit
        pass

#-------------------------------------------------------------------------------
# creating simple PDF file
#-------------------------------------------------------------------------------
class doTextPdf:

# NOTE: Font size tuning
# ----------------------
# THIS IS LOOKING SAME LIKE ORIGINAL PAPER PRINT
# Font size: 10.5
# Rows distance: 12.8
# MediaBox: [0 0 612 792]
# Text: BT 1 0 0 1 15 812
# Print: fit to size A4 210x297 mm
# ----------------------
# THIS IS LOOKING SAME LIKE ORIGINAL PAPER PRINT
# Font size: 10
# Rows distance: 12
# MediaBox: [0 0 612 792]
# Text: BT 1 0 0 1 17 780
# Print: original size A4 210x297 mm
# ----------------------

    def __init__(self):
        # initialize
        self.pdfFileName = ''
        self.pdfFileDescriptor = None

        # fonts array
        self.pdfFontsArray = []

        # default font, font size and distance
        self.pdfFont = 'F1'
        self.pdfFontSize = 10       # can by float
        self.pdfRowsDistance = 12   # can by float
        # lines per page (default 60), total pages (default 1)
        self.pdfMaxLinesPerPage = 60
        self.pdfTotalPages = 1

        # debug flag (True - write line numbers)
        self.debugFlag = False

        # temporary write buffer
        self.writeBuffer = ''

        # actual object position
        self.actualObjPosition = 0

        # PDF object reference table
        self.objectRefTable = []

        # first FONT object ID
        self.fontObjID = 4
        # fonts count
        self.fontObjCount = 0

        # first PAGE and CONTENT object ID
        #   (depend on FONT object count)
        self.pageObjID = 0
        self.contentObjID = 0
        # block PAGE + CONTENT size
        self.blockPageContentSize = 2

        # PDF Logo / Picture
        self.logoJpegData = ''
        self.logoSizeX = 0
        self.logoSizeY = 0
        self.logoPositionX = 300
        self.logoPositionY = 700

        # PDF basic objects
        self.OBJ_HEADER = "%PDF-1.4\n"
        self.OBJ_CATALOG = "1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n"
        self.OBJ_PAGES = "2 0 obj\n<< /Type /Pages /MediaBox [ 0 0 612 802 ] /Count %TOTALPAGES% /Kids [ %PAGE% ] >>\nendobj\n"
        self.OBJ_IMAGE = "3 0 obj\n<< /Type /XObject /Subtype /Image /ColorSpace /DeviceRGB /Width %WIDTH% /Height %HEIGHT% /BitsPerComponent 8 /Filter /DCTDecode /Length %IMAGELEN% >>\nstream\n%IMAGE%endstream\nendobj\n"
#TODO: add /Encoding /WinAnsiEncoding to OBJ_FONT
        self.OBJ_FONT = "%FONTOBJID% 0 obj\n<< /Type /Font /Subtype /Type1 /BaseFont /%FONTNAME% >>\nendobj\n"
        self.OBJ_PAGE = "%PAGEOBJID% 0 obj\n<< /Type /Page /Parent 2 0 R /Resources << /Font << %FONTS% >> /XObject << /Im1 3 0 R >> >> /Contents %CONTENTOBJID% 0 R >>\nendobj\n"
        self.OBJ_FONTS = "/%FONTNUM% %FONTOBJID% 0 R "
        self.OBJ_CONTENT = "%CONTENTOBJID% 0 obj\n<< /Length %TEXTLEN% >>\nstream\n%TEXT%endstream\nendobj\n"
        self.OBJ_LOGO = "q\n170 0 0 41 %LOGOPOSX% %LOGOPOSY% cm\n/Im1 Do\nQ\n"
        self.OBJ_TEXT = "BT\n1 0 0 1 17 780 Tm\n%ROWSDISTANCE% TL\n%TEXTLINES%ET\n"
        self.OBJ_XREF = "xref\n0 %TOTALOBJECTS%\n%XREFTAB%"
        self.OBJ_TRAILER = "trailer\n<< /Size %TOTALOBJECTS% /Root 1 0 R >>\nstartxref\n%XREFSTART%\n%%EOF"

        # PDF character conversion table
        self.CONV_TABLE = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, # 00 to 31
                           0,0,0,0,0,0,0,0,'\(','\)',0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, # 32 to 63
                           0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'\\\\',0,0,0, # 64 to 95
                           0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, # 96 to 127
                           0,0,0,0,0,0,0,0,0,0,'S',0,'S','T','Z','Z',           # 128 to 143
                           0,0,0,0,0,0,0,0,0,0,'s',0,'s','t','z','z',           # 144 to 159
                           0,0,0,'L',0,'A',0,0,0,'(c)','S',0,0,0,'(R)','Z',     # 160 to 175
                           0,0,0,0,0,0,0,0,0,'a','s',0,'L',0,'l','z',           # 176 to 191
                           'R','A','A','A','A','L','C','C','C','E','E','E','E','I','I','D', # 192 to 207
                           'D','N','N','O','O','O','O','x','R','U','U','U','U','Y','T','ss', # 208 to 223
                           'r','a','a','a','a','l','c','c','c','e','e','e','e','i','i','d', # 224 to 239
                           'd','n','n','o','o','o','o','/','r','u','u','u','u','y','t',0 ] # 240 to 255
        pass

    def readSettings(self, sConfigFileName):
        # logging
        sLog = "Init: reading PDF converter configuration..."
        print sLog
        logging.info(sLog)

        # open config file
        oConfig = ConfigParser.RawConfigParser()
        oConfig.read(sConfigFileName)

        # set config file section
        sSection = 'PDF Fonts'

        # read default font size from config file
        sKey = 'DefaultFontSize'
        try:
            self.pdfFontSize = oConfig.getint(sSection, sKey)
        except ConfigParser.Error:
            print "Warning: Could not found settings '%s' in section [%s]. Set to default '%d'" % (sKey,sSection,self.pdfFontSize)

        # read default font from config file
        sKey = 'DefaultFont'
        try:
            self.pdfFont = oConfig.get(sSection, sKey)
        except ConfigParser.Error:
            print "Warning: Could not found settings '%s' in section [%s]. Set to default '%s'" % (sKey,sSection,self.pdfFont)

        # read default rows distance from config file
        sKey = 'DefaultRowsDistance'
        try:
            self.pdfRowsDistance = oConfig.getint(sSection, sKey)
        except ConfigParser.Error:
            print "Warning: Could not found settings '%s' in section [%s]. Set to default '%d'" % (sKey,sSection,self.pdfRowsDistance)

        # read font size separator from config file
        sKey = 'FontSizeSeparator'
        sFontSizeSeparator = ','
        try:
            sFontSizeSeparator = oConfig.get(sSection, sKey)
        except ConfigParser.Error:
            print "Warning: Could not found settings '%s' in section [%s]. Set to default '%d'" % (sKey,sSection,sFontSizeSeparator)

        # read all fonts
        aPDFFonts = oConfig.items(sSection)
        # 0 = name, 1 = value
        for sPDFFont in aPDFFonts:
            # read font number
            sFontNum = sPDFFont[0].upper()
            # check F(num) key only
            if ((sFontNum[0] == 'F') and (sFontNum[1:].isdigit())):
                # prepare font name + font size
                (sFontName, sFontSize) = sPDFFont[1].split(sFontSizeSeparator)
                # normalize
                sFontName = sFontName.strip()
                iFontSize = int(sFontSize.strip())
                # add to global
                self.pdfFontsArray.append({'FontNum': sFontNum, 'FontName': sFontName, 'FontSize': iFontSize})
                # increment fonts count
                self.fontObjCount += 1
        # recalculate PAGE and CONTENT Obj ID
        self.pageObjID = self.fontObjID + self.fontObjCount
        self.contentObjID = self.fontObjID + self.fontObjCount + 1

        # read logo / picture
        sSection = 'PDF Logo'
        # read logo positions
        sKey = 'Position'
        sPositionsSeparator = ','
        try:
            sLogoPositions = oConfig.get(sSection, sKey)
            (sLogoPositionX, sLogoPositionY) = sLogoPositions.split(sPositionsSeparator)
            self.logoPositionX = int(sLogoPositionX.strip())
            self.logoPositionY = int(sLogoPositionY.strip())
        except ConfigParser.Error:
            print "Warning: Could not found settings '%s' in section [%s]. Set to default '%d, %d'" % (sKey,sSection,self.logoPositionX,self.logoPositionY)

        # read logo file name
        sKey = 'FileName'
        try:
            sLogoFileName = oConfig.get(sSection, sKey)
        except ConfigParser.Error:
            sLog = "ERROR: Could not found settings '%s' in section [%s]" % (sKey,sSection)
            print sLog
            logging.error(sLog)
            sys.exit(3)
        try:
            logoFP = open(sLogoFileName, 'rb')
            self.logoJpegData = logoFP.read()
            logoFP.close()
        except IOError:
            sLog = "ERROR: Could not read image file '%s' defined in settings '%s' in section [%s]" % (sLogoFileName, sKey, sSection)
            print sLog
            logging.error(sLog)
            sys.exit(3)
        # read logo image dimension
        iLogoImageDataIndex = self.logoJpegData.find('\xff\xc0') + 5
        self.logoSizeY, self.logoSizeX = struct.unpack('>HH', self.logoJpegData[iLogoImageDataIndex:iLogoImageDataIndex+4])

        # return
        pass

    def convertText(self, iLineNum, sTextLine):
        # initialize text output
        sTextOutput = ''
        # if debug mode, replace begining of TextLine with line number
        if (self.debugFlag):
            # prepare line numper as string
            sLineNum = "[%d]" % (iLineNum)
            iLineNumLen = len(sLineNum)
            sTextLine = sLineNum + sTextLine[iLineNumLen:]
        # convert given text line by CONV_TABLE
        for sChar in sTextLine:
            # convert to ascii code
            iChar = ord(sChar)
            if (self.CONV_TABLE[iChar] != 0):
                sChar = self.CONV_TABLE[iChar]
            sTextOutput += sChar
        # return converted text
        return sTextOutput

    def setPdfProperties(self, iPages, iLinesPerPage):
        # set total pages
        self.pdfTotalPages = iPages
        # set lines per page
        self.pdfMaxLinesPerPage = iLinesPerPage
        pass

    def createTextPdf(self, sPdfFileName):
        # logging
        sLog = " creating PDF file %s" % (sPdfFileName)
        print sLog
        logging.info(sLog)
        # initialize new empty PDF file name
        self.pdfFileName = sPdfFileName
        # reset actual object position
        self.actualObjPosition = 0
        # reset PDF object reference table
        self.objectRefTable = []
        # try to create PDF, mode wb
        try:
            self.pdfFileDescriptor = open(self.pdfFileName, 'wb')
        except IOError:
            sLog = "ERROR: Could not open file (%s) to write" % (self.pdfFileName)
            print sLog
            logging.error(sLog)
            sys.exit(3)
        pass

    def closeTextPdf(self):
        # logging
        sLog = " writing PDF file %s , Done" % (self.pdfFileName)
        print sLog
        logging.info(sLog)
        # close opened PDF
        self.pdfFileDescriptor.close()
        pass

    def getFontSizeByFontNum(self, sFontNum):
        # reset font size
        sFontSize = str(self.pdfFontSize)
        # look over
        for dFont in self.pdfFontsArray:
            # find font size
            if (sFontNum == dFont['FontNum']):
                sFontSize = dFont['FontSize']
                break
        # return
        return sFontSize

    def writeObject(self, sObject):
        # add current position to object reference table
        self.objectRefTable.append(self.actualObjPosition)
        # init write buffer
        self.writeBuffer = sObject
        # write to file
        self.rawWrite()
        # increment current position
        self.actualObjPosition += len(sObject)
        pass

    def writeObjPages(self):
        # init temp string
        sObjPages = self.OBJ_PAGES
        # prepare total pages count
        sObjPages = sObjPages.replace('%TOTALPAGES%', str(self.pdfTotalPages))
        # prepare page reference (e.g: 4 0 R 6 0 R ...)
        sPageReference = ''
        for iPageIndex in range(self.pdfTotalPages):
            sPageReference += "%d 0 R " % (self.pageObjID + (iPageIndex * self.blockPageContentSize))
        sObjPages = sObjPages.replace('%PAGE%', sPageReference.rstrip())
        # write object pages
        self.writeObject(sObjPages)
        pass

    def writeObjImage(self, bHasLogo):
        # init temp string
        sObjImage = self.OBJ_IMAGE
        if (bHasLogo == True):
            # replace %WIDTH%
            sObjImage = sObjImage.replace('%WIDTH%', str(self.logoSizeX))
            # replace %HEIGHT%
            sObjImage = sObjImage.replace('%HEIGHT%', str(self.logoSizeY))
            # replace %IMAGELEN%
            sObjImage = sObjImage.replace('%IMAGELEN%', str(len(self.logoJpegData)))
            # replace %IMAGE%
            sObjImage = sObjImage.replace('%IMAGE%', self.logoJpegData)
        else:
            sObjImage = sObjImage.replace('%WIDTH%', '0')
            sObjImage = sObjImage.replace('%HEIGHT%', '0')
            sObjImage = sObjImage.replace('%IMAGELEN%', '0')
            sObjImage = sObjImage.replace('%IMAGE%', '')
        # write object image
        self.writeObject(sObjImage)

    def writeObjFont(self):
        # loop over fonts array
        for iIndex in range(self.fontObjCount):
            # init temp string
            sObjFont = self.OBJ_FONT
            sObjFont = sObjFont.replace('%FONTOBJID%', str(self.fontObjID + iIndex))
            sObjFont = sObjFont.replace('%FONTNAME%', self.pdfFontsArray[iIndex]['FontName'])
            # write object font
            self.writeObject(sObjFont)
        # return
        pass

    def writeObjPageContent(self, aTextLines, aFonts, bHasLogo):
        # prepare values
        sObjText4ActualPage = ''
        iActualPage = 1
        sLastFontNum = ''
        # loop over all lines
        iTextLineTabLen = len(aTextLines)
        for iLineIndex in range(iTextLineTabLen):
            # font changed or is first line on page
            if ((sLastFontNum != aFonts[iLineIndex]) or (iLineIndex % self.pdfMaxLinesPerPage == 0)):
                if (iLineIndex % self.pdfMaxLinesPerPage == 0):
                    # new page allways begins with default font
                    # save default font as Last
                    sLastFontNum = self.pdfFont
                    # prepare font size
                    sFontSize = str(self.pdfFontSize)
                else:
                    # font changed
                    # save last font num
                    sLastFontNum = aFonts[iLineIndex]
                    # prepare font size
                    sFontSize = self.getFontSizeByFontNum(sLastFontNum)
                # change font num
                sObjText4ActualPage += "/%s %s Tf\n" % (sLastFontNum, sFontSize)
            # converto to 7-bit text
            sConvText = self.convertText((iLineIndex + 1), aTextLines[iLineIndex])
            # create text line, format: "(textline)'"
            sObjText4ActualPage += "(" + sConvText.rstrip() + ")'\n"
            # is last line on page ?
            if ((iLineIndex + 1) % self.pdfMaxLinesPerPage == 0):
                # write OBJ_PAGE + OBJ_CONTENT

                # prepare FONTS string
                sObjFontsArr = ''
                # loop over fonts array
                for iIndex in range(self.fontObjCount):
                    # init temp string
                    sObjFont = self.OBJ_FONTS
                    sObjFont = sObjFont.replace('%FONTNUM%', self.pdfFontsArray[iIndex]['FontNum'])
                    sObjFont = sObjFont.replace('%FONTOBJID%', str(self.fontObjID + iIndex))
                    # cumulate fonts array
                    sObjFontsArr += sObjFont

                # calculate actual Page an Content Obj ID
                iPageObjID = self.pageObjID + ((iActualPage-1) * self.blockPageContentSize)
                iContentObjID = self.contentObjID + ((iActualPage-1) * self.blockPageContentSize)

                # create object page
                sObjPage = self.OBJ_PAGE
                # replace %PAGEOBJID% with page object ID
                sObjPage = sObjPage.replace('%PAGEOBJID%', str(iPageObjID))
                # replace %FONTS% with fonts array
                sObjPage = sObjPage.replace('%FONTS%', sObjFontsArr)
                # replace %CONTENTOBJID% with content object ID
                sObjPage = sObjPage.replace('%CONTENTOBJID%', str(iContentObjID))
                # write object page
                self.writeObject(sObjPage)

                # create object text: "BT ... ET"
                sObjText = self.OBJ_TEXT
                sObjText = sObjText.replace('%ROWSDISTANCE%', str(self.pdfRowsDistance))
                sObjText = sObjText.replace('%TEXTLINES%', sObjText4ActualPage)

                # has logo ?
                if (bHasLogo == True):
                    # create object logo: "q ... Q"
                    sObjLogo = self.OBJ_LOGO
                    sObjLogo = sObjLogo.replace('%LOGOPOSX%', str(self.logoPositionX))
                    sObjLogo = sObjLogo.replace('%LOGOPOSY%', str(self.logoPositionY))
                else:
                    sObjLogo = ""

                # create object content
                sObjContent = self.OBJ_CONTENT
                # replace %CONTENTOBJID% with content object ID
                sObjContent = sObjContent.replace('%CONTENTOBJID%', str(iContentObjID))
                # replace %TEXT% with sObjText
                sObjContent = sObjContent.replace('%TEXT%', (sObjLogo + sObjText))
                # replace %TEXTLEN% with lenght sObjText
                sObjContent = sObjContent.replace('%TEXTLEN%', str(len(sObjLogo + sObjText)))
                # write object content
                self.writeObject(sObjContent)
                # reset actual page lines
                sObjText4ActualPage = ''
                # increment page number
                iActualPage += 1
        pass

    def writeObjFooter(self):
        # create reference table
        sRefTable = "0000000000 65535 f \n"
        iObjRefTableLen = len(self.objectRefTable)
        for iIndex in range(1,iObjRefTableLen):
            iOffset = self.objectRefTable[iIndex]
            sOffset = "%010d" % (iOffset)
            sRefTable += (sOffset + " 00000 n \n")
        # create xref object
        sObjXRef = self.OBJ_XREF
        # replace %XREFTAB% with reference table
        sObjXRef = sObjXRef.replace('%XREFTAB%', sRefTable)
        # replace %TOTALOBJECTS% with length of reference table
        sObjXRef = sObjXRef.replace('%TOTALOBJECTS%', str(iObjRefTableLen))
        # prepare trailer
        sObjTrailer = self.OBJ_TRAILER
        sObjTrailer = sObjTrailer.replace('%XREFSTART%', str(self.actualObjPosition))
        # replace %TOTALOBJECTS% with length of reference table
        sObjTrailer = sObjTrailer.replace('%TOTALOBJECTS%', str(iObjRefTableLen))
        # write reference table + trailer
        self.writeObject(sObjXRef + sObjTrailer)
        pass

    def rawWrite(self):
        # write buffer to file
        self.pdfFileDescriptor.write(self.writeBuffer)
        # clear write buffer
        self.writeBuffer = ''
        pass

    def writeTextPdf(self, aTextLines, aFonts, bHasLogo):
        self.writeObject(self.OBJ_HEADER)
        self.writeObject(self.OBJ_CATALOG)
        self.writeObjPages()
        self.writeObjImage(bHasLogo)
        self.writeObjFont()
        self.writeObjPageContent(aTextLines, aFonts, bHasLogo)
        self.writeObjFooter()
        # return PDF file name
        return self.pdfFileName

#-------------------------------------------------------------------------------
# prescribe to PDF fonts mapping
#-------------------------------------------------------------------------------
class prescribe2PdfFonts():

    def __init__(self):
        # initialize
        self.fontsMapping = []
        self.fontsCount = 0
        pass

    def readSettings(self, sConfigFileName):
        # logging
        sLog = "Init: reading Prescribe 2 PDF fonts mapping..."
        print sLog
        logging.info(sLog)

        # open config file
        oConfig = ConfigParser.RawConfigParser()
        oConfig.read(sConfigFileName)

        # set config file section
        sSection = 'Prescribe to PDF Fonts Mapping'

        # read all Prescribe fonts
        aFontsMapping = oConfig.items(sSection)
        # 0 = name, 1 = value
        for sFontMapping in aFontsMapping:
            # read Prescribe font number
            sPrescribeFont = sFontMapping[0].upper()
            # read PDF font number
            sPdfFont = sFontMapping[1].upper()
            # add to global
            self.fontsMapping.append({'FontPrescribe': sPrescribeFont, 'FontPDF': sPdfFont})
            # increment fonts count
            self.fontsCount += 1
        pass

    def getPdfFontName4PrescribeFont(self, sPrescribeFont):
        # initialize
        sPdfFontName = ''
        for dFontMapping in self.fontsMapping:
            # find Prescribe font
            if (dFontMapping['FontPrescribe'] == sPrescribeFont):
                # return PDF font
                return dFontMapping['FontPDF']

    def convertFonts(self, aFonts):
        # logging
        sLog = " converting Prescribe to PDF fonts"
        print sLog
        logging.info(sLog)
        # get fonts array length
        iFontsArrayLen = len(aFonts)
        for iIndex in range(iFontsArrayLen):
            # read Prescribe font name
            sPrescribeFontName = aFonts[iIndex]
            # get PDF font name for Prescribe font name
            sPdfFontName = self.getPdfFontName4PrescribeFont(sPrescribeFontName)
            # repleace it
            aFonts[iIndex] = sPdfFontName
        # return fonts array
        return aFonts
